#include "tofino/intrinsic_metadata.p4"
#include "tofino/stateful_alu_blackbox.p4"
#include "tofino/wred_blackbox.p4"
#include "tofino/lpf_blackbox.p4"
#include "tofino/meter_blackbox.p4"

header_type ethernet_t {
    fields {
        dstAddr : 48;
        srcAddr : 48;
        etherType : 16;
    }
}

header_type ipv4_t {
    fields {
        version : 4;
        ihl : 4;
        diffserv : 8;
        totalLen : 16;
        identification : 16;
        flags : 3;
        fragOffset : 13;
        ttl : 8;
        protocol : 8;
        hdrChecksum : 16;
        srcAddr : 32;
        dstAddr: 32;
    }
}

header_type meta_t {
    fields {
        hash_result : 16;
        dummy: 16;
        index1: 32;
        index2: 32;
        index3: 32;
        index4: 32;
        index5: 32;
    }
}


parser start {
    return parse_ethernet;
}

header ethernet_t ethernet;
header ipv4_t ipv4;
metadata meta_t meta;

parser parse_ethernet {
    extract(ethernet);
    return select(ethernet.etherType) {
        0x800 : parse_ipv4;
        default: ingress;
    }
}

parser parse_ipv4 {
    extract(ipv4);
    return ingress;
}

/* Field lists */

@pragma optional_field ipv4.dstAddr
field_list fl_1 {
    ethernet.etherType;
    ipv4.dstAddr;
    ipv4.srcAddr;
}

@pragma all_fields_optional
field_list fl_2 {
    ethernet.dstAddr;
    ipv4.ttl;
    ipv4.protocol;
}

field_list fl_3 {
    ethernet.srcAddr;
    ethernet.dstAddr;
}

field_list fl_4 {
    ipv4.protocol;
    12'8;
    ipv4.totalLen;
}


field_list fl_id {
    meta.hash_result;
}


/* Selectable hash algorithms */

field_list_calculation fl_calc {
   input {
       fl_1;
       fl_2;
       fl_3;
   }
   algorithm {
       crc_32;
       crc_32q;
       identity;
   }
   output_width : 32;
}

field_list_calculation id_calc {
    input {
        fl_id;
    }
    algorithm : identity;
    output_width : 16;
}

field_list_calculation cnt_calc {
    input {
       fl_1;
       fl_2;
    }
    algorithm {
       crc_16;
       identity;
    }
    output_width : 16;
}

field_list_calculation stfl_calc {
    input {
       fl_2;
       fl_3;
    }
    algorithm {
       crc_16;
       identity;
    }
    output_width : 12;
}

field_list_calculation wred_calc {
    input {
       fl_4;
       fl_1;
    }
    algorithm {
       identity;
       crc_8;
    }
    output_width : 8;
}

field_list_calculation lpf_calc {
    input {
       fl_3;
       fl_4;
    }
    algorithm {
       identity;
       crc_16;
    }
    output_width : 14;
}

field_list_calculation meter_calc_1 {
    input {
       fl_4;
       fl_2;
    }
    algorithm {
       crc_16;
       identity;
    }
    output_width : 13;
}


field_list_calculation meter_calc_2 {
    input {
       fl_1;
       fl_3;
    }
    algorithm {
       identity;
       crc_16;
    }
    output_width : 10;
}


/* Selector actions */

action do_nothing(){}

action sel_action_1(p0, p1){
    modify_field(ipv4.identification, p0);
    modify_field(ipv4.ttl, p1);
}

action sel_action_2(p0, p1, p2){
    modify_field(ethernet.etherType, p0);
    modify_field(ipv4.ttl, p1);
    modify_field(ipv4.identification, p2);
}

action sel_action_3(p0, p1, p2, p3){
    modify_field(ethernet.etherType, p0);
    modify_field(ipv4.ttl, p1);
    modify_field(ipv4.diffserv, p2);
    modify_field(ipv4.identification, p3);
}

action set_p(p){
   modify_field(ig_intr_md_for_tm.ucast_egress_port, p);
}

action set_dummy(p){
   modify_field(meta.dummy, p);
}

action calc_hash(){
   modify_field_with_hash_based_offset(meta.hash_result, 0, fl_calc, 65536);
}

counter cnt1 {
    type : packets;
    instance_count : 65536;
    min_width : 32;
}

action cnt_hash(){
   count_from_hash(cnt1, cnt_calc);
}


register reg1 {
    width : 32;
    instance_count : 4096;
}

blackbox stateful_alu bbox {
    reg: reg1;
    update_lo_1_value: register_lo + 1;
    output_value : alu_lo;
    output_dst : ipv4.srcAddr;
}

action stfl_hash(){
    bbox.execute_stateful_alu_from_hash(stfl_calc);
}

blackbox wred w {
    wred_input: ipv4.diffserv;
    drop_value : 15;
    no_drop_value : 3;
    instance_count : 256;
}

action wred_hash(){
    w.execute_from_hash(ipv4.protocol, wred_calc);
}

blackbox lpf pf {
   filter_input : ipv4.dstAddr;
   instance_count : 16384;
}

action lpf_hash(){
    pf.execute_from_hash(ipv4.protocol, lpf_calc);
}

blackbox meter my_meter_2 {
    type : bytes;
    instance_count : 8192;
    red_value : 42;
    yellow_value : 17;
    green_value : 4;
}

meter my_meter_1 {
    type : bytes; 
    instance_count : 1024;
}


action meter_hash_1(){
    execute_meter_from_hash(my_meter_1, meter_calc_1, ipv4.diffserv);
}


action meter_hash_2(){
    my_meter_2.execute_with_pre_color_from_hash(ipv4.diffserv, ipv4.diffserv, meter_calc_2);
}


action_selector the_sel {
    selection_key : id_calc;
    selection_mode : fair;
}

action_profile prof {
    actions {
       sel_action_1;
       sel_action_2;
       sel_action_3;
       do_nothing;
    }
    size : 4096;
    dynamic_action_selection : the_sel;
}


/* Tables */

table t_hash {
    actions {
        calc_hash;
    }
}

table t0 {
    reads {
        ipv4.srcAddr : exact;
    }
    action_profile : prof;
    size : 8192;
}

table t1 {
    reads {
        ipv4.identification : exact;
        meta.index1 : exact;
        meta.index2 : exact;
        meta.index3 : exact;
        meta.index4 : exact;
        meta.index5 : exact;
    }
    actions {
        do_nothing;
        set_p;
    }
    size : 256;
}

table t2 {
    actions {
       cnt_hash;
    }
    default_action : cnt_hash();
}

table t3 {
    actions {
       stfl_hash;
    }
    default_action : stfl_hash();
}

table t4 {
    actions {
       wred_hash;
    }
    default_action : wred_hash();
}

table t5 {
    actions {
       lpf_hash;
    }
    default_action : lpf_hash();
}

table t6 {
    actions {
       meter_hash_1;
    }
    default_action : meter_hash_1();
}

table t7 {
    actions {
       meter_hash_2;
    }
    default_action : meter_hash_2();
}

/* Control */

control ingress {
    apply(t_hash);
    apply(t0);
    apply(t1);
    apply(t2);
    apply(t3);
    apply(t4);
    apply(t5);
    apply(t6);
    apply(t7);
}
